package org.aphelie.torg.ruler.internal.core.text.character;

import java.util.ArrayList;

import org.aphelie.torg.ruler.core.IModelChangedEvent;
import org.aphelie.torg.ruler.core.character.ICharacterBase;
import org.aphelie.torg.ruler.core.character.ICharacterElement;
import org.aphelie.torg.ruler.core.character.ICharacterExtension;
import org.aphelie.torg.ruler.core.character.ICharacterExtensionPoint;
import org.aphelie.torg.ruler.core.character.ICharacterImport;
import org.aphelie.torg.ruler.core.character.ICharacterLibrary;
import org.aphelie.torg.ruler.core.character.ICharacterObject;
import org.aphelie.torg.ruler.core.character.ICharacterSketch;
import org.aphelie.torg.ruler.internal.core.character.CharacterSketch;
import org.aphelie.torg.ruler.internal.core.text.IDocumentElementNode;
import org.eclipse.core.runtime.CoreException;


public abstract class CharacterBaseNode extends CharacterObjectNode implements ICharacterBase {

    private static final long serialVersionUID = 1L;

    private String fSchemaVersion;
    

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#add(org.eclipse.pde.core.plugin.ICharacterLibrary)
     */
    public void add(ICharacterLibrary library) throws CoreException {
        IDocumentElementNode parent = getEnclosingElement("runtime", true); //$NON-NLS-1$
        if (library instanceof CharacterLibraryNode) {
            CharacterLibraryNode node = (CharacterLibraryNode) library;
            node.setModel(getModel());
            parent.addChildNode(node);
            fireStructureChanged(library, IModelChangedEvent.INSERT);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#add(org.eclipse.pde.core.plugin.ICharacterImport)
     */
    /* TODO
    public void add(ICharacterImport pluginImport) throws CoreException {
        IDocumentElementNode parent = getEnclosingElement("requires", true); //$NON-NLS-1$
        if (pluginImport instanceof CharacterImportNode) {
            CharacterImportNode node = (CharacterImportNode) pluginImport;
            parent.addChildNode(node);
            fireStructureChanged(pluginImport, IModelChangedEvent.INSERT);
        }
    }
    */

    /* TODO
    public void add(ICharacterImport[] pluginImports) {
        IDocumentElementNode parent = getEnclosingElement("requires", true); //$NON-NLS-1$
        for (int i = 0; i < pluginImports.length; i++) {
            if (pluginImports[i] != null && pluginImports[i] instanceof CharacterImportNode) {
                CharacterImportNode node = (CharacterImportNode) pluginImports[i];
                parent.addChildNode(node);
            }
        }
        fireStructureChanged(pluginImports, IModelChangedEvent.INSERT);
    }
    */

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#remove(org.eclipse.pde.core.plugin.ICharacterImport)
     */
    /* TODO
    public void remove(ICharacterImport pluginImport) throws CoreException {
        IDocumentElementNode parent = getEnclosingElement("requires", false); //$NON-NLS-1$
        if (parent != null) {
            parent.removeChildNode((IDocumentElementNode) pluginImport);
            pluginImport.setInTheModel(false);
            fireStructureChanged(pluginImport, IModelChangedEvent.REMOVE);
        }
    }
    */

    /* TODO
    public void remove(ICharacterImport[] pluginImports) {
        IDocumentElementNode parent = getEnclosingElement("requires", false); //$NON-NLS-1$
        if (parent != null) {
            for (int i = 0; i < pluginImports.length; i++) {
                parent.removeChildNode((IDocumentElementNode) pluginImports[i]);
                pluginImports[i].setInTheModel(false);
            }
            fireStructureChanged(pluginImports, IModelChangedEvent.REMOVE);
        }
    }
    */

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#getLibraries()
     */
    public ICharacterLibrary[] getLibraries() {
        ArrayList result = new ArrayList();
        IDocumentElementNode requiresNode = getEnclosingElement("runtime", false); //$NON-NLS-1$
        if (requiresNode != null) {
            IDocumentElementNode[] children = requiresNode.getChildNodes();
            for (int i = 0; i < children.length; i++) {
                if (children[i] instanceof ICharacterLibrary)
                    result.add(children[i]);
            }
        }

        return (ICharacterLibrary[]) result.toArray(new ICharacterLibrary[result.size()]);
    }
    
    public ICharacterSketch getSketch() {
        IDocumentElementNode requiresNode = getEnclosingElement("runtime", false);
        if (requiresNode != null) {
            IDocumentElementNode[] children = requiresNode.getChildNodes();
            for (int i = 0; i < children.length; i++) {
                if (children[i] instanceof ICharacterSketch) {
                    ICharacterSketch sk = (ICharacterSketch) children[i];
                    System.err.println("> " + children[i]);
                    System.err.println(">> " + sk);
       
                    return (ICharacterSketch) children[i];
                }
            }
        }
        IDocumentElementNode parent = getEnclosingElement("runtime", true);
        CharacterSketchNode node = (CharacterSketchNode) getCharacterModel().getCharacterFactory().createSketch();
        node.setModel(getModel());
        parent.addChildNode(node);
        fireStructureChanged(node, IModelChangedEvent.INSERT);
        return node;
    }

    private IDocumentElementNode getEnclosingElement(String elementName, boolean create) {
        CharacterElementNode element = null;
        IDocumentElementNode[] children = getChildNodes();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof ICharacterElement) {
                if (((CharacterElementNode) children[i]).getXMLTagName().equals(elementName)) {
                    element = (CharacterElementNode) children[i];
                    break;
                }
            }
        }
        if (element == null && create) {
            element = new CharacterElementNode();
            element.setXMLTagName(elementName);
            element.setParentNode(this);
            element.setModel(getModel());
            element.setInTheModel(true);
            if (elementName.equals("runtime")) { //$NON-NLS-1$
                addChildNode(element, 0);
            } else if (elementName.equals("requires")) { //$NON-NLS-1$
                if (children.length > 0 && children[0].getXMLTagName().equals("runtime")) { //$NON-NLS-1$
                    addChildNode(element, 1);
                } else {
                    addChildNode(element, 0);
                }
            }
        }
        return element;
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#getImports()
     */
    public ICharacterImport[] getImports() {
        ArrayList result = new ArrayList();
        IDocumentElementNode requiresNode = getEnclosingElement("requires", false); //$NON-NLS-1$
        if (requiresNode != null) {
            IDocumentElementNode[] children = requiresNode.getChildNodes();
            for (int i = 0; i < children.length; i++) {
                if (children[i] instanceof ICharacterImport)
                    result.add(children[i]);
            }
        }

        return (ICharacterImport[]) result.toArray(new ICharacterImport[result.size()]);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#getProviderName()
     */
    public String getProviderName() {
        return getXMLAttributeValue(K_PROVIDER);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#getVersion()
     */
    public String getVersion() {
        return getXMLAttributeValue(P_VERSION);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#remove(org.eclipse.pde.core.plugin.ICharacterLibrary)
     */
    public void remove(ICharacterLibrary library) throws CoreException {
        IDocumentElementNode parent = getEnclosingElement("runtime", false); //$NON-NLS-1$
        if (parent != null) {
            parent.removeChildNode((IDocumentElementNode) library);
            library.setInTheModel(false);
            fireStructureChanged(library, IModelChangedEvent.REMOVE);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#setProviderName(java.lang.String)
     */
    public void setProviderName(String providerName) throws CoreException {
        setXMLAttribute(K_PROVIDER, providerName);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#setVersion(java.lang.String)
     */
    public void setVersion(String version) throws CoreException {
        setXMLAttribute(P_VERSION, version);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#swap(org.eclipse.pde.core.plugin.ICharacterLibrary, org.eclipse.pde.core.plugin.ICharacterLibrary)
     */
    public void swap(ICharacterLibrary l1, ICharacterLibrary l2) throws CoreException {
        IDocumentElementNode node = getEnclosingElement("runtime", false); //$NON-NLS-1$
        if (node != null) {
            node.swap((IDocumentElementNode) l1, (IDocumentElementNode) l2);
            firePropertyChanged(node, P_LIBRARY_ORDER, l1, l2);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#getSchemaVersion()
     */
    public String getSchemaVersion() {
        return fSchemaVersion;
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#setSchemaVersion(java.lang.String)
     */
    public void setSchemaVersion(String schemaVersion) throws CoreException {
        fSchemaVersion = schemaVersion;
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#add(org.eclipse.pde.core.plugin.ICharacterExtension)
     */
    public void add(ICharacterExtension extension) throws CoreException {
        if (extension instanceof CharacterExtensionNode) {
            CharacterExtensionNode node = (CharacterExtensionNode) extension;
            node.setModel(getModel());
            addChildNode(node);
            fireStructureChanged(extension, IModelChangedEvent.INSERT);
        }
    }

    /**
     * @param extension
     * @param position
     * @throws CoreException
     */
    public void add(ICharacterExtension extension, int position) throws CoreException {
        // TODO: MP: DND: Make API?
        if ((extension instanceof CharacterExtensionNode) == false) {
            return;
        } else if ((position < 0) || (position > getChildCount())) {
            return;
        }
        CharacterExtensionNode node = (CharacterExtensionNode) extension;
        node.setModel(getModel());
        addChildNode(node, position);
        fireStructureChanged(extension, IModelChangedEvent.INSERT);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#add(org.eclipse.pde.core.plugin.ICharacterExtensionPoint)
     */
    /* TODO
    public void add(ICharacterExtensionPoint extensionPoint) throws CoreException {
        if (extensionPoint instanceof CharacterExtensionPointNode) {
            CharacterExtensionPointNode node = (CharacterExtensionPointNode) extensionPoint;
            node.setModel(getModel());
            extensionPoint.setInTheModel(true);
            node.setParentNode(this);
            ICharacterExtensionPoint[] extPoints = getExtensionPoints();
            if (extPoints.length > 0)
                addChildNode(node, indexOf((IDocumentElementNode) extPoints[extPoints.length - 1]) + 1);
            else {
                IDocumentElementNode requires = getEnclosingElement("requires", false); //$NON-NLS-1$
                if (requires != null) {
                    addChildNode(node, indexOf(requires) + 1);
                } else {
                    IDocumentElementNode runtime = getEnclosingElement("runtime", false); //$NON-NLS-1$
                    if (runtime != null)
                        addChildNode(node, indexOf(runtime) + 1);
                    else
                        addChildNode(node, 0);
                }
            }
            fireStructureChanged(extensionPoint, IModelChangedEvent.INSERT);
        }
    }
    */

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#getExtensionPoints()
     */
    public ICharacterExtensionPoint[] getExtensionPoints() {
        ArrayList result = new ArrayList();
        IDocumentElementNode[] children = getChildNodes();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof ICharacterExtensionPoint)
                result.add(children[i]);
        }
        return (ICharacterExtensionPoint[]) result.toArray(new ICharacterExtensionPoint[result.size()]);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#getExtensions()
     */
    public ICharacterExtension[] getExtensions() {
        ArrayList result = new ArrayList();
        IDocumentElementNode[] children = getChildNodes();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof ICharacterExtension)
                result.add(children[i]);
        }
        return (ICharacterExtension[]) result.toArray(new ICharacterExtension[result.size()]);
    }

    public int getIndexOf(ICharacterExtension e) {
        ICharacterExtension[] children = getExtensions();
        for (int i = 0; i < children.length; i++) {
            if (children[i].equals(e))
                return i;
        }
        return -1;
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#remove(org.eclipse.pde.core.plugin.ICharacterExtension)
     */
    public void remove(ICharacterExtension extension) throws CoreException {
        if (extension instanceof IDocumentElementNode) {
            removeChildNode((IDocumentElementNode) extension);
            extension.setInTheModel(false);
            fireStructureChanged(extension, IModelChangedEvent.REMOVE);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#remove(org.eclipse.pde.core.plugin.ICharacterExtensionPoint)
     */
    public void remove(ICharacterExtensionPoint extensionPoint) throws CoreException {
        if (extensionPoint instanceof IDocumentElementNode) {
            removeChildNode((IDocumentElementNode) extensionPoint);
            extensionPoint.setInTheModel(false);
            fireStructureChanged(extensionPoint, IModelChangedEvent.REMOVE);
        }
    }

    public void remove(ICharacterObject node) {
        if (node instanceof IDocumentElementNode) {
            removeChildNode((IDocumentElementNode) node);
            node.setInTheModel(false);
            fireStructureChanged(node, IModelChangedEvent.REMOVE);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.IExtensions#swap(org.eclipse.pde.core.plugin.ICharacterExtension, org.eclipse.pde.core.plugin.ICharacterExtension)
     */
    public void swap(ICharacterExtension e1, ICharacterExtension e2) throws CoreException {
        swap((IDocumentElementNode) e1, (IDocumentElementNode) e2);
        firePropertyChanged(this, P_EXTENSION_ORDER, e1, e2);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterBase#swap(org.eclipse.pde.core.plugin.ICharacterImport, org.eclipse.pde.core.plugin.ICharacterImport)
     */
    public void swap(ICharacterImport import1, ICharacterImport import2) throws CoreException {
        IDocumentElementNode node = getEnclosingElement("requires", false); //$NON-NLS-1$
        if (node != null) {
            node.swap((IDocumentElementNode) import1, (IDocumentElementNode) import2);
            firePropertyChanged(node, P_IMPORT_ORDER, import1, import2);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.IIdentifiable#getId()
     */
    public String getId() {
        return getXMLAttributeValue(K_ID);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.IIdentifiable#setId(java.lang.String)
     */
    public void setId(String id) throws CoreException {
        setXMLAttribute(K_ID, id);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterObject#getName()
     */
    public String getName() {
        return getXMLAttributeValue(K_NAME);
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.core.plugin.ICharacterObject#setName(java.lang.String)
     */
    public void setName(String name) throws CoreException {
        setXMLAttribute(K_NAME, name);
    }
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#getPlayer()
     */
    public String getPlayer() {
        return getXMLAttributeValue(K_PLAYER);
    }
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#setPlayer(java.lang.String)
     */
    public void setPlayer(String value) throws CoreException {
        setXMLAttribute(K_PLAYER, value);
    }
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#getChronicle()
     */
    public String getChronicle() {
        return getXMLAttributeValue(K_CHRONICLE);
    }
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#setChronicle(java.lang.String)
     */
    public void setChronicle(String value) throws CoreException {
        setXMLAttribute(K_CHRONICLE, value);
    }   
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#getStrain()
     */
    public String getStrain() {
        return getXMLAttributeValue(K_STRAIN);
    }
    
    /* (non-Javadoc)
     * @see org.aphelie.torg.ruler.core.character.ICharacterBase#setStrain(java.lang.String)
     */
    public void setStrain(String value) throws CoreException {
        setXMLAttribute(K_STRAIN, value);
    }    

    /* (non-Javadoc)
     * @see org.eclipse.pde.internal.ui.model.plugin.CharacterObjectNode#write()
     */
    public String write(boolean indent) {
        String newLine = getLineDelimiter();

        StringBuffer buffer = new StringBuffer();
        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + newLine); //$NON-NLS-1$
        buffer.append("<?eclipse version=\"3.0\"?>" + newLine); //$NON-NLS-1$

        buffer.append(writeShallow(false) + newLine);

        IDocumentElementNode runtime = getEnclosingElement("runtime", false); //$NON-NLS-1$
        if (runtime != null) {
            runtime.setLineIndent(getLineIndent() + 3);
            buffer.append(runtime.write(true) + newLine);
        }

        IDocumentElementNode requires = getEnclosingElement("requires", false); //$NON-NLS-1$
        if (requires != null) {
            requires.setLineIndent(getLineIndent() + 3);
            buffer.append(requires.write(true) + newLine);
        }

        ICharacterExtensionPoint[] extPoints = getExtensionPoints();
        for (int i = 0; i < extPoints.length; i++) {
            IDocumentElementNode extPoint = (IDocumentElementNode) extPoints[i];
            extPoint.setLineIndent(getLineIndent() + 3);
            buffer.append(extPoint.write(true) + newLine);
        }

        ICharacterExtension[] extensions = getExtensions();
        for (int i = 0; i < extensions.length; i++) {
            IDocumentElementNode extension = (IDocumentElementNode) extensions[i];
            extension.setLineIndent(getLineIndent() + 3);
            buffer.append(extension.write(true) + newLine);
        }
        
        ICharacterSketch sketch = getSketch();
        IDocumentElementNode node = (IDocumentElementNode) sketch;
        node.setLength(getLineIndent() + 3);
        buffer.append(node.write(true) + newLine);

        buffer.append("</" + getXMLTagName() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
        return buffer.toString();
    }

    /* (non-Javadoc)
     * @see org.eclipse.pde.internal.ui.model.plugin.CharacterObjectNode#writeShallow()
     */
    public String writeShallow(boolean terminate) {
        String newLine = System.getProperty("line.separator"); //$NON-NLS-1$
        StringBuffer buffer = new StringBuffer();
        buffer.append("<" + getXMLTagName()); //$NON-NLS-1$
        buffer.append(newLine);

        String id = getId();
        if (id != null && id.trim().length() > 0)
            buffer.append("   " + K_ID + "=\"" + getWritableString(id) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

        String name = getName();
        if (name != null && name.trim().length() > 0)
            buffer.append("   " + K_NAME + "=\"" + getWritableString(name) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

        String player = getPlayer();
        if (player != null && player.trim().length() > 0)
            buffer.append("   " + K_PLAYER + "=\"" + getWritableString(player) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

        String chronicle = getChronicle();
        if (chronicle != null && chronicle.trim().length() > 0)
            buffer.append("   " + K_CHRONICLE + "=\"" + getWritableString(chronicle) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$        

        String strain = getStrain();
        if (strain != null && strain.trim().length() > 0)
            buffer.append("   " + K_STRAIN + "=\"" + getWritableString(strain) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        
        String version = getVersion();
        if (version != null && version.trim().length() > 0)
            buffer.append("   " + P_VERSION + "=\"" + getWritableString(version) + "\"" + newLine); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

        String provider = getProviderName();
        if (provider != null && provider.trim().length() > 0) {
            buffer.append("   " + K_PROVIDER + "=\"" + getWritableString(provider) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

        String[] specific = getSpecificAttributes();
        for (int i = 0; i < specific.length; i++)
            buffer.append(newLine + specific[i]);
        if (terminate)
            buffer.append("/"); //$NON-NLS-1$
        buffer.append(">"); //$NON-NLS-1$

        return buffer.toString();
    }

    protected abstract String[] getSpecificAttributes();

    public boolean isRoot() {
        return true;
    }
}
